/*
 * Composition Knowledge Miner. A data mashup composition knowledge miner
 * Copyright (C) 2011 Carlos Rodriguez
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

package eu.ict_omelette.mashup.compositionminer;

import eu.ict_omelette.mashup.adapters.pipes.PipesAdapter;
import eu.ict_omelette.mashup.canonicalmodel.CanonicalMashup;
import eu.ict_omelette.mashup.canonicalmodel.Component;
import eu.ict_omelette.mashup.canonicalmodel.Dataflow;
import eu.ict_omelette.mashup.canonicalmodel.InputParameter;
import eu.ict_omelette.mashup.compositionminer.util.Carpenter;
import eu.ict_omelette.mashup.compositionminer.util.Util;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;

/**
 *
 * @author Carlos Rodriguez
 */
public class ComponentEmbeddingMiner {
    private LinkedList<CanonicalMashup> compRepo;

    private static String SEPARATOR = "&nbsp;";
    private static String EQUAL = ":=>";

    public ComponentEmbeddingMiner(LinkedList<CanonicalMashup> compRepo){
        this.compRepo = compRepo;
    }

    public List<CanonicalMashup> mine(Double minSupEm, Double minSupDf, Double minSupDm, Double minSupVa){

        //Here we mine the frequent component embeddings
        LinkedHashMap lhmRecEm = getRecurrentEmbeddings(minSupEm);
        //System.out.println("Leading pattern count: " + lhmRecEm.size());
        LinkedList<CanonicalMashup> freqRepo = getEmbeddingInstances(lhmRecEm);       

        //Here we mine the frequent recurrent Dataflows
        LinkedHashMap lhmRecDf = this.getRecurrentDfs(freqRepo, minSupDf);
        freqRepo = getDfsInstances(freqRepo, lhmRecDf);

        freqRepo = this.getFrequentParamValue(freqRepo, minSupVa);



        /*
        //This fragment of code is just for computing statistics
        Iterator it = lhmRecEm.keySet().iterator();
        freqRepo = new LinkedList();
        while(it.hasNext()){
            it.next();
            freqRepo.add(new CanonicalMashup());
        }
         */

        //System.out.println("RESULSET SIZE: " + freqRepo.size());



        /*
        for(int i = 0; i < freqRepo.size(); i++){
            CanonicalMashup cm = freqRepo.get(i);
            Util.printCanonicalMashup(cm);
        }
         */
        

               
        return freqRepo;
    }

    //This method creates a signature of the Embedding with Dataflow in the form of:
    //A,B,X(Y). This means that A and B preceeds X (and have their own dataflows
    //that connect them to X) and X embeds Y
    private String getEmbDfSignature(CanonicalMashup cm){

        Component targetComp = new Component();
        LinkedList<String> sourceComps = new LinkedList();
        Iterator<Dataflow> itDf = cm.getDataFlows();
        while(itDf.hasNext()){
            Dataflow df = itDf.next();
            targetComp = df.getToComponent();
            sourceComps.add(df.getFromComponent().getType());            
        }

        Collections.sort(sourceComps);

        String sig = "";
        for(int i = 0; i < sourceComps.size(); i++){
            sig += sourceComps.get(i) + ",";
        }

        Iterator<Component> itComp = targetComp.getEmbeddedComponents();
        String embeddedComp = "";
        while(itComp.hasNext()){
            embeddedComp = itComp.next().getType();
        }

        sig += targetComp.getType() + "(" + embeddedComp + ")";
        

        return sig;
    }

    private String getStrParamValAssig(Component comp, boolean isEmbedded){
        String strVa = "";
        Iterator<InputParameter> itIp = comp.getInputParameters();
        while(itIp.hasNext()){
            InputParameter ip = itIp.next();

            //If the input parameter is not a "data mapped" parameter
            if(ip.getDataMapping().equals(PipesAdapter.FALSE) &&
               (!ip.getValue().equals(""))){
                
                //We put a mark for embedded components
                if(isEmbedded)
                    strVa += "()";
                
                strVa += comp.getType() + "." + ip.getName() + EQUAL + ip.getValue() + SEPARATOR;
            }
        }

        return strVa;
    }

    private LinkedList<CanonicalMashup> getFrequentParamValue(LinkedList<CanonicalMashup> freqRepo, double minSupVa){
        LinkedHashMap<String, LinkedList> lhmSig = new LinkedHashMap();
        LinkedList<CanonicalMashup> resRepo = new LinkedList();

        //Here, we put in hash map all the canonical models, indexed by the
        //signature returned by the function *getEmbDfSignature()*
        for(int i = 0; i < freqRepo.size(); i++){
            CanonicalMashup cm = freqRepo.get(i);
            String sig = getEmbDfSignature(cm);

            LinkedList ll = lhmSig.get(sig);
            if(ll == null){
                ll = new LinkedList();
                ll.add(cm);
            }else{
                ll.add(cm);
            }
            lhmSig.put(sig, ll);
        }

        //here we iterate over all the signatures we have and then mine
        //parameter value patterns from the models that correspond to the
        //same signature
        Iterator<String> itSig = lhmSig.keySet().iterator();
        while(itSig.hasNext()){
            String sig = itSig.next();
            LinkedList<CanonicalMashup> ll = lhmSig.get(sig);
            LinkedList<String> strDB = new LinkedList();

            //We get the value assignments for the input parameters of the component
            Iterator<CanonicalMashup> itCm = ll.iterator();
            while(itCm.hasNext()){
                CanonicalMashup cm = itCm.next();
                String strVa = "";

                Iterator<Component> itComp = cm.getComponents();
                while(itComp.hasNext()){
                    Component comp = itComp.next();

                    
                    strVa = strVa + getStrParamValAssig(comp, false);

                    Iterator<Component> itEmb = comp.getEmbeddedComponents();
                    while(itEmb.hasNext()){
                        Component embComp = itEmb.next();
                        strVa = strVa + getStrParamValAssig(embComp, true);
                    }
                }

                strDB.add(strVa);
                //System.out.println(strVa);
            }

            LinkedHashMap<String, Integer> hashToNbr = new LinkedHashMap();
            LinkedHashMap<Integer, String> hashToStr = new LinkedHashMap();
            int idx = 0;
            String[] tranDB = new String[strDB.size()];

            for(int i = 0; i < strDB.size(); i++){
                String line = strDB.get(i);
                String[] elemVec = line.split(SEPARATOR);
                tranDB[i] = "";
                //System.out.println("\n");
                
                for(int j = 0; j < elemVec.length; j++){

                    String elem = elemVec[j];
                    
                    if(hashToNbr.get(elem) == null){
                        hashToNbr.put(elem, idx);
                        hashToStr.put(idx, elem);
                        idx++;
                    }
                    //System.out.print(hashToNbr.get(elem) + " ");

                    tranDB[i] += hashToNbr.get(elem) + " ";
                    
                }

                //System.out.println();

                tranDB[i] = tranDB[i].trim();

                //System.out.println(tranDB[i]);

            }

            try{

                //System.out.println("\n\nRESULT:");
                Carpenter car = new Carpenter(tranDB, minSupVa);
                List<int[]> listPatterns = car.mine();


                //We take one exemplary canonical mashup for the current signature and
                //clean up the parameter values before re-filling them with
                //the *frequent* parameter values
                CanonicalMashup cmClean = ll.get(0);
                Iterator<Component> itComp = cmClean.getComponents();
                while(itComp.hasNext()){
                    Component comp = itComp.next();
                    Iterator<InputParameter> itIp = comp.getInputParameters();
                    while(itIp.hasNext()){
                        InputParameter ip = itIp.next();
                        if(ip.getDataMapping().equals(PipesAdapter.FALSE)){
                            ip.setValue("");
                        }
                    }

                    Iterator<Component> itEmb = comp.getEmbeddedComponents();
                    if(itEmb.hasNext()){
                        Component embCo = itEmb.next();
                        itIp = comp.getInputParameters();
                        while(itIp.hasNext()){
                            InputParameter ip = itIp.next();
                            if(ip.getDataMapping().equals(PipesAdapter.FALSE)){
                                ip.setValue("");
                            }
                        }
                    }
                }


                //System.out.println("######### " + sig);
                
                //And here we assign the parameter values that are frequent
                //to the canonical mashups
                for(int i = 0; i < listPatterns.size(); i++){
                    int[] line = listPatterns.get(i);

                    //We clone() the clean version of the canonical model
                    CanonicalMashup cm = cmClean.clone();

                    for(int j=0; j < line.length; j++){

                        //System.out.print(line[j] + "  ");

                        String sign = (String)hashToStr.get(line[j]);
                        
                        Iterator<Component> itCo = cm.getComponents();
                        while(itCo.hasNext()){
                            Component comp = itCo.next();

                            String[] pair = sign.split(EQUAL);
                            String param = pair[0];
                            String value = "";

                            if(pair.length == 2)
                                value=pair[1];

                            //if the parameter corresponds to a embedded component
                            if(param.contains("()")){

                                if(!comp.getIsEmbedding()){
                                    continue;
                                }else{
                                    Iterator<Component> itEmb = comp.getEmbeddedComponents();
                                    if(itEmb.hasNext()){

                                        Component embCo = itEmb.next();
                                        Iterator<InputParameter> itIp = embCo.getInputParameters();
                                        while(itIp.hasNext()){
                                            InputParameter ip = itIp.next();

                                            if(param.contains(embCo.getType()) && param.contains(ip.getName())){
                                                ip.setValue(value);
                                            }
                                        }

                                    }

                                }
                            }else{
                                Iterator<InputParameter> itIp = comp.getInputParameters();
                                while(itIp.hasNext()){
                                    InputParameter ip = itIp.next();

                                    if(param.contains(comp.getType()) && param.contains(ip.getName())){
                                        ip.setValue(value);
                                    }
                                }

                            }

                        }

                    }

                    //System.out.println("");

                    resRepo.add(cm);
                }


                /*
                System.out.println("\n\n");

                for(int k = 0; k < freqRepo.size(); k++){
                    CanonicalMashup cm = freqRepo.get(k);
                    Util.printCanonicalMashup(cm);
                }
                 */

                
                /*
                //DEBUGGING CODE
                for(int i = 0; i < listPatterns.size(); i++){
                    int[] line = listPatterns.get(i);

                    for(int j=0; j < line.length; j++){
                        System.out.println(hashToStr.get(line[j]));
                        //System.out.print(line[j] + " ");
                    }

                    System.out.println();
                }
                 */
                 

                


            }catch(Exception e){
                
                e.printStackTrace();
                
            }


            
        }

        return resRepo;
      
    }

    private LinkedList<CanonicalMashup> getDfsInstances(LinkedList freqRepo, LinkedHashMap lhmRecDf){


        Iterator<CanonicalMashup> itCm = freqRepo.iterator();
        while(itCm.hasNext()){
            CanonicalMashup cm = itCm.next();

            //Here we remove those dataflows that are not "frequent"
            Iterator<Dataflow> itDf = cm.getDataFlows();
            while(itDf.hasNext()){
                Dataflow df = itDf.next();
                String sig = getDfSignature(df);

                if(lhmRecDf.get(sig) == null)
                        itDf.remove();

            }

            //If no Dataflows remain in the CanonicalMashup instance,
            //we delete the CanonicalMashup (it cannot be considered as an Embedding Pattern)
            itDf = cm.getDataFlows();
            if(!itDf.hasNext()){
                itCm.remove();
                continue;
            }

            //Here we delete any component that remained "dangling" because
            //of the deletion of "infrequent" Dfs above
            Iterator<Component> itComp = cm.getComponents();
            while(itComp.hasNext()){
                Component comp = itComp.next();
                boolean compExists = false;
                itDf = cm.getDataFlows();
                while(itDf.hasNext()){
                    Dataflow df = itDf.next();
                    if(df.getFromComponent().getId().equals(comp.getId()) ||
                       df.getToComponent().getId().equals(comp.getId())){
                        compExists = true;
                        break;
                    }                        
                }

                if(!compExists){
                    itComp.remove();
                }
                
                
            }
            
        }

        return freqRepo;
        
    }

    private String getDfSignature(Dataflow df){
        String sig = df.getFromComponent().getType() + "(" +
                             df.getFromPort().getName() + ")" +
                             "=>" +
                             df.getToComponent().getType() + "(" +
                             df.getToPort().getName() + ")";

        return sig;
    }

    private LinkedHashMap<String, Integer> getRecurrentDfs(LinkedList<CanonicalMashup> freqRepo, double minSupDf){
        LinkedHashMap<String, Integer> hashDfGlobalCount = new LinkedHashMap();

        Iterator<CanonicalMashup> itCm = freqRepo.iterator();
        while(itCm.hasNext()){
            CanonicalMashup cm = itCm.next();
            
            LinkedHashMap<String, String> hashDfMashCount = new LinkedHashMap();

            Iterator<Dataflow> itDf = cm.getDataFlows();
            while(itDf.hasNext()){
                Dataflow df = itDf.next();
                String sig = getDfSignature(df);
                //System.out.println(sig);
                hashDfMashCount.put(sig, sig);                
            }

            Iterator<String> itMash = hashDfMashCount.keySet().iterator();
            while(itMash.hasNext()){
                String sig = itMash.next();
                Integer count = hashDfGlobalCount.get(sig);
                if(count == null){
                    count = new Integer(1);
                }else{
                    count++;
                }

                //System.out.println(sig + " : " + count);

                hashDfGlobalCount.put(sig, count);                
            }
            
        }

        Iterator<String> itGlobal = hashDfGlobalCount.keySet().iterator();
        while(itGlobal.hasNext()){
            String sig = itGlobal.next();
            Integer count = hashDfGlobalCount.get(sig);

            double supp = (double)count / (double)freqRepo.size();
            if(supp < minSupDf){
                itGlobal.remove();
            }
            //else{
            //    System.out.println(sig);
            //}

        }        

        return hashDfGlobalCount;
    }

    //This method gets the instances of Embedding Patterns, including also the
    //component that preceeds the "embedding" component (x->y(z))
    private LinkedList<CanonicalMashup> getEmbeddingInstances(LinkedHashMap lhmRecEm){
        LinkedList<CanonicalMashup> freqRepo = new LinkedList();
        Iterator<String> it = lhmRecEm.keySet().iterator();
        
        while(it.hasNext()){
            String freqSig = it.next();

            Iterator<CanonicalMashup> itCm = compRepo.iterator();
            while(itCm.hasNext()){
                CanonicalMashup cm = itCm.next();

                CanonicalMashup newCm = new CanonicalMashup();

                Iterator<Component> itComp = cm.getComponents();
                while(itComp.hasNext()){
                    Component comp = itComp.next();

                    if(comp.getIsEmbedding()){
                        String compSig = getEmbSignature(comp);

                        if(compSig.equals(freqSig)){                            
                            newCm.addComponent(comp);
                            Iterator<Dataflow> itDf = cm.getDataFlows();
                            while(itDf.hasNext()){
                                Dataflow df = itDf.next();
                                if(df.getToComponent().getId().equals(comp.getId())){                                    
                                    newCm.addDataflow(df);
                                    newCm.addComponent(df.getFromComponent());
                                }
                            }
                            
                        }

                    }

                }

                if(newCm.getComponents().hasNext()){
                    freqRepo.add(newCm);
                    //Util.printCanonicalMashup(newCm);
                }
            }

        }

        return freqRepo;
    }

    private String getEmbSignature(Component comp){

        Iterator<Component> embIt = comp.getEmbeddedComponents();

        //For the case of YAHOO PIPES we consider just one embeded component
        if(embIt.hasNext()){
            Component embComp = embIt.next();
            return comp.getName() + "(" + embComp.getName() + ")";
        }else{
            return "";
        }

    }

    private LinkedHashMap<String, Integer> getRecurrentEmbeddings(double minSupEm){

        //This is a global counter of embeddings
        LinkedHashMap<String, Integer> hashGlobalCount = new LinkedHashMap();
        
        Iterator<CanonicalMashup> itMash =  compRepo.iterator();
        while(itMash.hasNext()){
            CanonicalMashup cm = itMash.next();
            
            //This is to detect component embeddings in mashup. We use this to
            //avoid duplicate counting of identical embeddings that appear in the
            //same mashup
            LinkedHashMap<String, String> hashMashCount = new LinkedHashMap();

            Iterator<Component> itComp = cm.getComponents();
            while(itComp.hasNext()){
                Component comp = itComp.next();

                if(comp.getIsEmbedding()){
                    String sigEmb = getEmbSignature(comp);

                    if(!sigEmb.contains("pipe:")){
                        hashMashCount.put(sigEmb, sigEmb);
                        //System.out.println(sigEmb);
                    }
                }

            }

            Iterator<String> itSig = hashMashCount.keySet().iterator();
            while(itSig.hasNext()){
                String key = itSig.next();
                Integer count = hashGlobalCount.get(key);

                if(count == null){
                    count = new Integer(1);                    
                }else{
                    count++;
                }

                hashGlobalCount.put(key, count);
                
            }

        }

        Iterator<String> it = hashGlobalCount.keySet().iterator();
        while(it.hasNext()){
            String key = it.next();
            Integer count = hashGlobalCount.get(key);

            double supp = (double)count / (double)compRepo.size();

            //We keep only those embeddings that has a support of at least minSupEm
            if(supp < minSupEm){
                it.remove();
            }
            //else{
            //    System.out.println(key);
            //}
        }

        

        return hashGlobalCount;
        
    }



}
